import { RequestMethodsEnum } from "@/enums/requestEnums";
import axios, {
  AxiosError,
  type AxiosInstance,
  type AxiosRequestConfig,
  type AxiosResponse,
} from "axios";
import { isFunction, merge, cloneDeep } from "lodash";
import axiosCancel from "./cancel";
import type { RequestData, RequestOptions } from "./type";

export class Axios {
  private axiosInstance: AxiosInstance;
  private readonly config: AxiosRequestConfig;
  private readonly options: RequestOptions;
  constructor(config: AxiosRequestConfig) {
    this.config = config;
    this.options = config.requestOptions;
    this.axiosInstance = axios.create(config);
    this.setupInterceptors();
  }

  /**
   * @description 获取axios实例
   */
  getAxiosInstance() {
    return this.axiosInstance;
  }

  /**
   * @description 设置拦截器
   */
  setupInterceptors() {
    if (!this.config.axiosHooks) {
      return;
    }
    const {
      requestInterceptorsHook,
      requestInterceptorsCatchHook,
      responseInterceptorsHook,
      responseInterceptorsCatchHook,
    } = this.config.axiosHooks;
    this.axiosInstance.interceptors.request.use(
      (config) => {
        this.addCancelToken(config);
        if (isFunction(requestInterceptorsHook)) {
          config = requestInterceptorsHook(config);
        }
        return config;
      },
      (err: Error) => {
        if (isFunction(requestInterceptorsCatchHook)) {
          requestInterceptorsCatchHook(err);
        }
        return err;
      }
    );
    this.axiosInstance.interceptors.response.use(
      (response: AxiosResponse<RequestData>) => {
        this.removeCancelToken(response.config.url!);
        if (isFunction(responseInterceptorsHook)) {
          response = responseInterceptorsHook(response);
        }
        return response;
      },
      (err: AxiosError) => {
        if (isFunction(responseInterceptorsCatchHook)) {
          responseInterceptorsCatchHook(err);
        }
        if (err.code != AxiosError.ERR_CANCELED) {
          this.removeCancelToken(err.config?.url!);
        }

        if (
          err.code == AxiosError.ECONNABORTED ||
          err.code == AxiosError.ERR_NETWORK
        ) {
          return new Promise((resolve) => setTimeout(resolve, 500)).then(() =>
            this.retryRequest(err)
          );
        }
        return Promise.reject(err);
      }
    );
  }

  /**
   * @description 添加CancelToken
   */
  addCancelToken(config: AxiosRequestConfig) {
    const { ignoreCancelToken } = config.requestOptions;
    !ignoreCancelToken && axiosCancel.add(config);
  }

  /**
   * @description 移除CancelToken
   */
  removeCancelToken(url: string) {
    axiosCancel.remove(url);
  }

  /**
   * @description 重新请求
   */
  retryRequest(error: AxiosError) {
    const config = error.config;
    const { retryCount, isOpenRetry } = config.requestOptions;
    if (
      !isOpenRetry ||
      config.method?.toUpperCase() == RequestMethodsEnum.POST
    ) {
      return Promise.reject(error);
    }
    config.retryCount = config.retryCount ?? 0;

    if (config.retryCount >= retryCount) {
      return Promise.reject(error);
    }
    config.retryCount++;

    return this.axiosInstance.request(config);
  }
  /**
   * @description get请求
   */
  get<T = any>(
    config: Partial<AxiosRequestConfig>,
    options?: Partial<RequestOptions>
  ): Promise<T> {
    return this.request({ ...config, method: RequestMethodsEnum.GET }, options);
  }

  /**
   * @description post请求
   */
  post<T = any>(
    config: Partial<AxiosRequestConfig>,
    options?: Partial<RequestOptions>
  ): Promise<T> {
    return this.request(
      { ...config, method: RequestMethodsEnum.POST },
      options
    );
  }

  /**
   * @description 请求函数
   */
  request<T = any>(
    config: Partial<AxiosRequestConfig>,
    options?: Partial<RequestOptions>
  ): Promise<any> {
    const opt: RequestOptions = merge({}, this.options, options);
    const axioxConfig: AxiosRequestConfig = {
      ...cloneDeep(config),
      requestOptions: opt,
    };
    const { urlPrefix } = opt;
    // 拼接请求前缀如api
    if (urlPrefix) {
      axioxConfig.url = `${urlPrefix}${config.url}`;
    }
    return new Promise((resolve, reject) => {
      this.axiosInstance
        .request<any, AxiosResponse<RequestData<T>>>(axioxConfig)
        .then((res) => {
          resolve(res);
        })
        .catch((err) => {
          reject(err);
        });
    });
  }
}
